package com.biao.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.biao.common.to.mq.SeckillOrderTo;
import com.biao.common.utils.R;
import com.biao.common.vo.MemberRespVo;
import com.biao.seckill.config.ThreadLocalConfig;
import com.biao.seckill.feign.CouponFeignService;
import com.biao.seckill.feign.ProductFeignService;
import com.biao.seckill.service.SeckillService;
import com.biao.seckill.to.SeckillSkuRedisTo;
import com.biao.seckill.vo.SeckillSessionsWithSkus;
import com.biao.seckill.vo.SeckillSkuVo;
import com.biao.seckill.vo.SkuInfoVo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;
    //抢购详情
    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    //商品详情
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";
    //信号量
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+商品随机码


    /**
     * 上架秒杀商品
     */
    @Override
    public void upSeckillSkuLatest3Days() {
        //1.扫描需要参与秒杀的活动
        R session = couponFeignService.getLates3DaySession();
        if (session.getCode() == 0) {
            //上架商品
            List<SeckillSessionsWithSkus> sessionData = session.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {
            });
            //缓存到redis
            //1.缓存活动信息
            saveSessionInfos(sessionData);
            //2.缓存活动的关联商品信息
            saveSessionSkuInfos(sessionData);
        }
    }

    /**
     * @return 找到符合时间范围的商品详情, 进行上架
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        List<SeckillSkuRedisTo> all = new ArrayList<>();
        long nowTime = System.currentTimeMillis();
        //redis中的促销场次结构 seckill:sessions:1675526400000_1675533600000
        Set<String> keys = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        for (String key : keys) {
            String[] split = key.replace(SESSIONS_CACHE_PREFIX, "").split("_");
            long beginTime = Long.parseLong(split[0]);
            long endTime = Long.parseLong(split[1]);
            if (nowTime >= beginTime && nowTime <= endTime) {
                List<String> range = stringRedisTemplate.opsForList().range(key, 0, -1);
                BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> multiGet = ops.multiGet(range);
                if (multiGet != null && multiGet.size() > 0) {
                    List<SeckillSkuRedisTo> collect = multiGet.stream().map(item -> {
                        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(item, SeckillSkuRedisTo.class);
                        return seckillSkuRedisTo;
                    }).collect(Collectors.toList());

                    all.addAll(collect);
                    continue;
                }


            }
        }


        return all;
    }

    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        //1.找到所有需要参与秒杀的商品的key
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        //正则，先获取所有key（1_2）然后在服务器代码中进行正则验证
        String regx = "\\d_" + skuId;
        if (keys == null || keys.size() == 0) {
            return null;
        }
        for (String key : keys) {
            //正则匹配
            if (!Pattern.matches(regx, key)) {
                continue;
            }
            String json = hashOps.get(key);
            SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
            //随机码处理
            long currentTime = System.currentTimeMillis();
            if (!(currentTime >= redisTo.getStartTime() && currentTime <= redisTo.getEndTime())) {
                redisTo.setRandomCode(null);
            }
            return redisTo;

        }

        return null;
    }

    @Override
    public String kill(String killId, String key, Integer num) {

        MemberRespVo respVo = ThreadLocalConfig.get();
        //1.获取当前秒杀商品详细信息
        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String json = ops.get(killId);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        //2.判断合法性校验（拦截器已判断是否登录)
        SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
        //2.1校验时间合法性
        Long startTime = redisTo.getStartTime();
        Long endTime = redisTo.getEndTime();
        long nowTime = System.currentTimeMillis();
        long ttl = endTime - nowTime;//毫秒
        //校验时间
        if (nowTime < startTime || nowTime > endTime) {
            return null;
        }
        //2.2校验随机码和商品id
        String randomCode = redisTo.getRandomCode();
        String id = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();

        if (!randomCode.equals(key) || !killId.equals(id)) {
            return null;
        }
        //2.3验证购买数量
        if (num > redisTo.getSeckillLimit()) {
            return null;
        }

        //2.4验证是否购买过商品(幂等性)
        //setnx不存在的时候才占位
        String redisKey = respVo.getId() + "_" + id;
        //自动过期
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
        if (aBoolean==null||!aBoolean) {
            //买过了
            return null;
        }
        //成功表示用户第一次购买
        //3.获取信号量tryAcquire()非阻塞方法
        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
        boolean b = semaphore.tryAcquire(num);//获取信号量
        if (!b) {
            return null;
        }
        //秒杀成功，快速下单,返回订单号
        String orderSn = IdWorker.getTimeId();
        //4.向队列发送消息
        SeckillOrderTo orderTo = new SeckillOrderTo();
        orderTo.setOrderSn(orderSn);
        orderTo.setMemberId(respVo.getId());
        orderTo.setNum(num);
        orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
        orderTo.setSkuId(redisTo.getSkuId());
        orderTo.setSeckillPrice(redisTo.getSeckillPrice());
        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);
        return orderSn;

    }


    private void saveSessionInfos(List<SeckillSessionsWithSkus> sessions) {
        if (sessions != null)
            sessions.stream().forEach(session -> {
                Long startTime = session.getStartTime().getTime();
                Long endTime = session.getEndTime().getTime();
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                Boolean hasKey = stringRedisTemplate.hasKey(key);
                if (!hasKey) {
                    List<SeckillSkuVo> relationSkus = session.getRelationSkus();
                    if (relationSkus == null || relationSkus.size() == 0) {
                        return;
                    }
                    //因为是stringRedisTemplate，所有返回类型为String
                    List<String> collect = relationSkus.stream().map(
                            item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString()).collect(Collectors.toList());
                    stringRedisTemplate.opsForList().leftPushAll(key, collect);
                }
            });
    }

    private void saveSessionSkuInfos(List<SeckillSessionsWithSkus> sessions) {
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        if (sessions != null)
            sessions.stream().forEach(session -> {
                //1.随机码(防止攻击)

                session.getRelationSkus().stream().forEach(seckillSkuVo -> {
                    String token = UUID.randomUUID().toString().replace("-", "");
                    if (!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString())) {
                        //缓存商品hash结构
                        SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                        //2.缓存秒杀商品信息(sku信息)
                        R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                        if (skuInfo.getCode() == 0) {
                            SkuInfoVo info = skuInfo.getData2("skuInfo", new TypeReference<SkuInfoVo>() {
                            });
                            redisTo.setSkuInfoVo(info);
                        }
                        //3.缓存秒杀商品基本信息
                        BeanUtils.copyProperties(seckillSkuVo, redisTo);
                        //4.设置当前商品的秒杀时间信息
                        redisTo.setStartTime(session.getStartTime().getTime());
                        redisTo.setEndTime(session.getEndTime().getTime());
                        redisTo.setRandomCode(token);
                        String jsonString = JSON.toJSONString(redisTo);
                        ops.put(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(), jsonString);
                        //如果当前场次的商品库存信息已经上架，就不需要再上架
                        //5.引入分布式信号量(限流)
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        //商品可以秒杀的数量作为信号量
                        semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
                    }
                });
            });
    }


}
